package org.nf.web.servlet.mapping;

import cn.hutool.core.text.AntPathMatcher;
import jakarta.servlet.http.HttpServletRequest;
import org.nf.web.servlet.FrameworkContext;
import org.nf.web.servlet.HandlerExecutionChain;
import org.nf.web.servlet.HandlerMethod;
import org.nf.web.servlet.annotation.PathVariable;
import org.nf.web.servlet.annotation.RequestMapping;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * @author wangliang
 * @date 2023/4/19
 * 根据请求url找到对应的请求处理方法，这些处理方法都会使用RequestMapping注解标注，
 * 在初始化时会通过扫描的class查找带有RequestMapping注解的方法并封装HandlerMethod
 * 缓存到map中
 */
public class RequestMappingHandlerMapping extends AbstractHandlerMapping {
    /**
     * AntPath匹配
     */
    private final AntPathMatcher matcher = new AntPathMatcher();
    /**
     * 保存请求方法的信息，key是请求的uri地址，value是一个HandleMethod对象
     */
    private final Map<String, HandlerMethod> handlerMethodMap = new HashMap<>();

    /**
     * 初始化HandlerMapping
     * 根据包名扫描对应的类，并解析出带有@RequestMapping注解的方法，将其收集起来
     */
    public RequestMappingHandlerMapping() {
        Set<Class<?>> classes = FrameworkContext.getInstance().getClasses();
        resolveClass(classes);
    }

    /**
     * 解析Class找出带有注解的Class
     * @param classes 扫描的class集合
     */
    private void resolveClass(Set<Class<?>> classes) {
        //循环遍历集合并加载Class对象
        for(Class<?> clazz : classes) {
            //如果类上声明了@RequestMapping注解，则作为请求的前缀或命名空间
            String prefix = "";
            if(clazz.isAnnotationPresent(RequestMapping.class)) {
                prefix = clazz.getAnnotation(RequestMapping.class).value();
            }
            //解析类中带有注解的所有method
            resolveMethods(clazz, prefix);
        }
    }

    /**
     * 解析带有注解的方法
     * @param handlerClass handler的Class
     * @param prefix url前缀
     */
    private void resolveMethods(Class<?> handlerClass, String prefix) {
        //循环遍历方法集合，找出带有注解的Method对象
        for (Method method : handlerClass.getMethods()) {
            if (method.isAnnotationPresent(RequestMapping.class)) {
                //将请求的前缀与方法请求的地址合并为完整的请求url
                String uri = prefix + method.getAnnotation(RequestMapping.class).value();
                //将method对象、method对象的参数信息、目标class封装成一个HandlerMethod
                HandlerMethod handlerMethod = new HandlerMethod(method, method.getParameters(), handlerClass);
                //如果存在HandlerMethodMap中存在此uri，则抛出异常
                if(handlerMethodMap.containsKey(uri)) {
                    throw new RuntimeException("This uri already exists：" + uri);
                }
                //将HandlerMethod保存到handlerMethodMap中
                handlerMethodMap.put(uri, handlerMethod);
            }
        }
    }

    @Override
    public HandlerExecutionChain getHandler(HttpServletRequest request) {
        String uri = getRequestURI(request);
        HandlerMethod handlerMethod = handlerMethodMap.get(uri);
        if (handlerMethod == null) {
            if(lfuCache.containsKey(uri)) {
                String antPath = lfuCache.get(uri);
                handlerMethod = handlerMethodMap.get(antPath);
                //将key保存到请求作用域，用于路径参数解析
                request.setAttribute(PathVariable.class.getName(), antPath);
            } else {
                Optional<String> keyOptional = handlerMethodMap.keySet().stream()
                        .filter(path -> matcher.match(path, uri)).findAny();
                if(keyOptional.isPresent()) {
                    handlerMethod = handlerMethodMap.get(keyOptional.get());
                    //缓存uri映射
                    lfuCache.put(uri, keyOptional.get());
                    //将key保存到请求作用域，用于路径参数解析
                    request.setAttribute(PathVariable.class.getName(), keyOptional.get());
                }
            }
        }
        //返回拦截器链
        return getHandlerExecutionChain(request, handlerMethod);
    }
}
